use ahash::AHashMap;
use eframe::egui;
use std::hash::Hash;

pub struct ButtonFactory<K> {
    pub image: Option<egui::TextureHandle>,
    pub uv_ratios: AHashMap<K, (egui::Rect, f32)>,
    pub has_frame: bool,
}

impl<K: Hash + Eq + Copy> ButtonFactory<K> {
    pub fn new(has_frame: bool) -> Self {
        ButtonFactory {
            image: None,
            uv_ratios: AHashMap::new(),
            has_frame,
        }
    }

    pub fn initialize(
        &mut self,
        ctx: &egui::Context,
        name: &str,
        image_bytes: &[u8],
        buttons: &[(K, usize, usize, usize, usize)],
    ) {
        self.initialize_with_mirror(ctx, name, image_bytes, buttons, false);
    }

    pub fn initialize_mirrored(
        &mut self,
        ctx: &egui::Context,
        name: &str,
        image_bytes: &[u8],
        buttons: &[(K, usize, usize, usize, usize)],
    ) {
        self.initialize_with_mirror(ctx, name, image_bytes, buttons, true);
    }

    pub fn initialize_with_mirror(
        &mut self,
        ctx: &egui::Context,
        name: &str,
        image_bytes: &[u8],
        buttons: &[(K, usize, usize, usize, usize)],
        mirror: bool,
    ) {
        let (header, image_data) = rapid_qoi::Qoi::decode_alloc(image_bytes).unwrap();
        let (width, height) = (header.width as usize, header.height as usize);
        let mut color_image =
            egui::ColorImage::from_rgba_unmultiplied([width, height], &image_data);
        if mirror {
            for (_, x, y, w, h) in buttons {
                let (x, y, w, h) = (*x, *y, *w, *h);
                for row in y..=(y + h) {
                    for col in 0..(w / 2) {
                        let left = row * width + x + col;
                        let right = row * width + x + (w - 1 - col);
                        color_image.pixels.swap(left, right);
                    }
                }
            }
        }
        self.image = Some(ctx.load_texture(name, color_image, egui::TextureOptions::LINEAR));

        let (width, height) = (width as f32, height as f32);
        self.uv_ratios.reserve(buttons.len());
        for (key, x, y, w, h) in buttons {
            let (x, y, w, h) = (*x as f32, *y as f32, *w as f32, *h as f32);
            self.uv_ratios.insert(
                *key,
                (
                    egui::Rect::from_min_size(
                        egui::Pos2::new(x / width, y / height),
                        egui::Vec2::new(w / width, h / height),
                    ),
                    w / h,
                ),
            );
        }
    }

    pub fn get_image(&self, key: K, height: f32) -> egui::Image {
        let button_image = self.image.as_ref().unwrap();
        let (uv, ratio) = self.uv_ratios[&key];
        let width = height * ratio;
        egui::Image::new(button_image, egui::Vec2::new(width, height)).uv(uv)
    }

    pub fn get_button(&self, key: K, height: f32) -> egui::ImageButton {
        let button_image = self.image.as_ref().unwrap();
        let (uv, ratio) = self.uv_ratios[&key];
        let width = height * ratio;
        egui::ImageButton::new(button_image, egui::Vec2::new(width, height))
            .frame(self.has_frame)
            .uv(uv)
    }

    pub fn get_disabled_button(&self, key: K, height: f32) -> egui::ImageButton {
        self.get_button(key, height).tint(egui::Color32::GRAY)
    }
}
